surface: Always set PHASE_PAINT as pending when updates are scheduled
authorCarlos Garnacho <carlosg@gnome.org>
Wed, 7 Apr 2021 19:04:28 +0000 (21:04 +0200)
committerCarlos Garnacho <carlosg@gnome.org>
Wed, 7 Apr 2021 21:06:06 +0000 (23:06 +0200)
At times (most often when closing subsurfaces that are scheduling
relayouts) the PHASE_PAINT handling gets broken with the following
sequence:

1. Surface receives wl_callback.done for the previous frame.
   Surface is thawed.
2. A new update on the surface is scheduled. PHASE_PAINT is
   requested directly on the frame clock. priv->pending_phase is
   left unset in the surface.
3. Surface gets frozen
4. Frame clock processes the update scheduled at 2. The surface
   is frozen, so paint is prevented. PHASE_PAINT is considered
   handled.
5. Compositor emits wl_callback.done again. Surface is thawed.
6. At this point the machinery is off
   - The surface didn't paint but has pending update regions
   - priv->draw_needed is set in the toplevel and other portions
     of the widget tree
   - So queueing redraws is ineffective at eventually calling
     gdk_surface_schedule_update() again on the toplevel surface.
   - We don't paint anymore, so this broken state is not flushed
     until other subsurface changes manage to schedule the missing
     update.

To fix this, always set PHASE_PAINT in priv->pending_phase when
doing gdk_surface_schedule_update(). If the frame clock turns
around before the surface is thawed, it will still be waiting to
be processed the next iteration.

Fixes: https://gitlab.gnome.org/GNOME/gtk/-/issues/3750
gdk/gdksurface.c

index c55a9a8042d60a3fe7861157cf76aaf4ad8dd339..ef6464c43a82412a61c8983b2fd44465061dd981 100644 (file)
@@ -1299,12 +1299,11 @@ gdk_surface_schedule_update (GdkSurface *surface)
 
   g_return_if_fail (surface);
 
+  surface->pending_phases |= GDK_FRAME_CLOCK_PHASE_PAINT;
+
   if (surface->update_freeze_count ||
       gdk_surface_is_toplevel_frozen (surface))
-    {
-      surface->pending_phases |= GDK_FRAME_CLOCK_PHASE_PAINT;
-      return;
-    }
+    return;
 
   /* If there's no frame clock (a foreign surface), then the invalid
    * region will just stick around unless gdk_surface_process_updates()